/* ESP32C3 UART Driver Implementation */

#include "uart.h"
#include <stdint.h>
#include <stddef.h>

/* UART registers base address */
#define UART0_BASE_ADDR 0x60000000
#define UART1_BASE_ADDR 0x60000400

/* UART registers definition */
typedef struct {
    uint32_t UART_RXFIFO;        /* Offset: 0x00 */
    uint32_t UART_TXFIFO;        /* Offset: 0x04 */
    uint32_t UART_ST;            /* Offset: 0x08 */
    uint32_t UART_CONF0;         /* Offset: 0x0C */
    uint32_t UART_CONF1;         /* Offset: 0x10 */
    uint32_t UART_ClKDIV;        /* Offset: 0x14 */
    uint32_t UART_FIFO_CONF;     /* Offset: 0x18 */
    uint32_t UART_SW_FLOW_CONF;  /* Offset: 0x1C */
    uint32_t UART_INT_RAW;       /* Offset: 0x20 */
    uint32_t UART_INT_ST;        /* Offset: 0x24 */
    uint32_t UART_INT_ENA;       /* Offset: 0x28 */
    uint32_t UART_INT_CLR;       /* Offset: 0x2C */
    uint32_t UART_CLKDIV_FRAG;   /* Offset: 0x30 */
    uint32_t UART_AUTOBAUD;      /* Offset: 0x34 */
    uint32_t UART_AUTOBAUD_CONF; /* Offset: 0x38 */
    uint32_t UART_RS485_CONF;    /* Offset: 0x3C */
    uint32_t UART_IRDA_CONF;     /* Offset: 0x40 */
    uint32_t UART_IRDA_PULSE;    /* Offset: 0x44 */
    uint32_t UART_DMA_CONF;      /* Offset: 0x48 */
    uint32_t UART_VERSION;       /* Offset: 0x4C */
} uart_reg_t;

#define UART0 ((uart_reg_t *)UART0_BASE_ADDR)
#define UART1 ((uart_reg_t *)UART1_BASE_ADDR)

/* Get UART register */
static inline uart_reg_t *get_uart_reg(uart_num_t uart_num)
{
    return (uart_num == UART_NUM_0) ? UART0 : UART1;
}

/* UART initialization */
void uart_init(uart_num_t uart_num, uart_config_t *config)
{
    uart_reg_t *uart = get_uart_reg(uart_num);
    
    /* Reset UART */
    uart->UART_CONF0 = 0;
    
    /* Configure baud rate */
    uint32_t clk_div = 40000000 / config->baud_rate;
    uart->UART_ClKDIV = clk_div;
    uart->UART_CLKDIV_FRAG = 0;
    
    /* Configure data bits, parity, stop bits */
    uint32_t conf0 = 0;
    conf0 |= (config->data_bits - 5) << 1; /* DATA_BITS */
    conf0 |= (config->parity) << 3; /* PARITY_EN, PARITY */
    conf0 |= (config->stop_bits - 1) << 5; /* STOP_BITS */
    conf0 |= (1 << 0); /* UART_EN */
    uart->UART_CONF0 = conf0;
    
    /* Enable FIFO */
    uart->UART_FIFO_CONF = (1 << 0) | (1 << 1); /* RXFIFO_RST, TXFIFO_RST */
    uart->UART_FIFO_CONF = (1 << 4) | (1 << 5); /* RX_FIFO_FULL_THRHD, TX_FIFO_EMPTY_THRHD */
    
    /* Enable UART */
    uart->UART_CONF0 |= (1 << 0);
}

/* Write bytes to UART */
void uart_write_bytes(uart_num_t uart_num, const uint8_t *data, size_t size)
{
    uart_reg_t *uart = get_uart_reg(uart_num);
    
    for (size_t i = 0; i < size; i++) {
        /* Wait until TX FIFO is not full */
        while ((uart->UART_ST & (1 << 4)) != 0);
        uart->UART_TXFIFO = data[i];
    }
}

/* Write a single character to UART */
void uart_write_char(uart_num_t uart_num, uint8_t ch)
{
    uart_write_bytes(uart_num, &ch, 1);
}

/* Read bytes from UART */
size_t uart_read_bytes(uart_num_t uart_num, uint8_t *data, size_t size, uint32_t timeout_ms)
{
    uart_reg_t *uart = get_uart_reg(uart_num);
    size_t read_bytes = 0;
    
    while (read_bytes < size) {
        /* Check if there is data in RX FIFO */
        if (uart->UART_ST & (1 << 0)) {
            data[read_bytes++] = uart->UART_RXFIFO & 0xFF;
        } else {
            /* No data, check timeout */
            if (timeout_ms == 0) {
                break;
            }
            /* TODO: Implement proper timeout */
            timeout_ms--;
        }
    }
    
    return read_bytes;
}

/* Read a single character from UART */
int uart_read_char(uart_num_t uart_num, uint32_t timeout_ms)
{
    uint8_t ch;
    size_t read = uart_read_bytes(uart_num, &ch, 1, timeout_ms);
    return (read == 1) ? ch : -1;
}
